Fix stuck model switches from false end-failure in session takeover - #1308
Fix stuck model switches from false end-failure in session takeover#1308nordicnode wants to merge 1 commit into
Conversation
|
Nice work. Pulling the takeover decision out into The logic itself holds up: treating One gap: there's no test exercising the hook branch itself — the Good scope discipline otherwise — no touches to forbidden paths, no drive-by reformatting of the pre-existing prettier violations you called out. |
The model_locked takeover only released rows that were active on the locked model, so ended-within-grace rows (the stale rows the branch exists for) and already-released rows produced an explanation claiming the end had failed when no DELETE was ever attempted, locking the user out of their pick for the session hour (CodebuffAI#1298). Route the follow-up GET through a pure planner that separates release (live row attributable to the lock) from retry (row already gone, re-POST the consumed pick once) from explain (real read/delete failure or an unattributable row), and act on it from the branch itself. The branch is extracted as runModelLockedTakeover with its collaborators injected, so the retry path, the one-retry bound, and the message-only-on-attempt behavior are covered by tests that drive production code instead of reasoning about the diff.
79715fd to
aa465f9
Compare
|
Follow-up on the hook-branch ask: the branch is now production code with its collaborators injected, so it is testable without mounting the hook.
Gates, repo-pinned bun 1.3.14: Two known limits, stated rather than papered over: the four wiring lines in |
Problem & Context
#1298: picking a model while the server still holds a session row can strand the user for the full hour. In the
model_lockeddeliberate-pick takeover (cli/src/hooks/use-freebuff-session.ts), the held row was released only when it wasstatus === 'active'and matched the lock's model. Two real shapes fail that guard with no DELETE ever attempted:endedrows still inside the server's grace window (with an instance id) — the stale crashed-CLI rows this branch exists to clear, and the shapeholdsLiveFreebuffSlotalready treats as slot-holding;Either one made the client report "ending it failed, run /end-session" — a thing it never tried — and the user waited out the hour.
Changes Made
planModelLockedSwitch(cli/src/utils/freebuff-session-api.ts) maps the takeover's follow-up GET row torelease(active on the locked model, orendedinside grace, where the DELETE replays the refund receipt via the instance id),retry(no row left), orexplain(unattributable; a different model's row is never deleted).runModelLockedTakeover(cli/src/hooks/use-freebuff-session.ts) holds the wholemodel_lockeddecision — the deliberate-pick guard, the follow-up GET, the DELETE, and both chat notices — withfetchHeld/releaseSlot/notify/isStaleinjected, returningrepick|revert|stale. The tick maps that outcome onto the scheduling it already did.takeFreebuffExplicitPick(). That read-and-clear is the one-retry bound: the marker annotates exactly one response, so a lock that races back after the retry's re-POST reverts in silence instead of looping./end-session, the withdrawn-model fallback flip, and all server contracts are unchanged.Verification
tsc --noEmit -p .incli(TypeScript 5.5.4): exit 0.clisuite: 3065 pass / 18 fail at head vs 3046 pass / 18 fail at base3f00c772a. Failing set identical — 16packages/internal/src/envimport errors (paths absent from the public snapshot) plus 2 pre-existing/reasoningassertion reds. The delta is +19 tests: 6 planner, 13 branch.bun test src/hooks/__tests__/model-locked-takeover.test.ts src/utils/__tests__/freebuff-session-api.test.ts→ 28 pass / 0 fail.runModelLockedTakeover/planModelLockedSwitch/ the marker clear —retrytreated asexplain, the silent re-POST notices, either staleness gate dropped, the deliberate-pick guard dropped, a foreign-model row released, an ended-past-grace row released, the success and failure notices dropped, the two model labels swapped in one message, the marker no longer cleared — each turns the suite red; every one reverted.Porting note:
runModelLockedTakeover,ModelLockedTakeoverDeps,ModelLockedTakeoverOutcome,noteFreebuffExplicitPickandtakeFreebuffExplicitPickhave no consumers outsideuse-freebuff-session.ts. They exist so the branch is reachable from a test without mounting the hook — the repo's dependency-injection-over-mock.module()rule rules out the alternative. The lines insidetickthat turn an outcome intonextMethod/schedule(0)are unchanged from the current code and are still asserted by reading, not by a test.This fixes the client-side stuck state and the false report in #1298; the server's row lifecycle itself is outside this repo. Related to #1298.